/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
 */
package hermesV2;

import hermesV2.agent.Agent;
import hermesV2.agent.ServiceAgent;
import hermesV2.basicServices.communication.CommunicationManager;
import hermesV2.basicServices.discovery.DiscoveryManager;
import hermesV2.basicServices.genesis.GenesisManager;
import hermesV2.basicServices.mobility.MobilityManager;
import hermesV2.basicServices.monitoring.Monitor;
import hermesV2.basicServices.security.SecurityManager;
import hermesV2.core.id.IdManager;
import hermesV2.core.net.SendReceive;
import hermesV2.core.starter.AgentStarter;
import hermesV2.util.HermesLogger;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.InvocationTargetException;
import java.util.Properties;

/**
 * It is the main class of the platform. It represents the server to accept and
 * to launch new agents, to receive and to send messages It makes new instance
 * of the different components of the platform and links every components.
 * 
 * @author Diego Bonura
 */
public class Place {
    /** The home directory of the platform */
    private static String hermesHome = null;

    /** Name of the properties file (by default "hermes.properties") */
    private String filePropertiesName = "hermes.properties";

    /** System properties */
    private Properties properties = null;
    
    /** The current PlaceDescriptor. */
    private static PlaceDescriptor currentPlaceDescriptor;

    /** Rapresents the <code>SendReceive</code> component */
    private SendReceive sendReceive;

    /** Rappresents the <code>AgentStarter</code> component */
    private AgentStarter agentStarter;

    /**
     * Rappresents the <code>SecurityManager</code> component
     */
    private SecurityManager securityManager;

    /**
     * Manages the identificators of agents and handles the position of agent
     * been born in the current place.
     */
    private IdManager idManager;

    /**
     * The reference of the Mobility component. Offers the mobility service to
     * the agent layer.
     */
    private MobilityManager mobilityManager;

    /**
     * The reference of the Discovery component. Offers a way to know every
     * service agents registered into the place.
     */
    private DiscoveryManager discoveryManager;

    /**
     * The reference of the Genesis component. It could starts new agents, clone
     * new agents and it handles the agent death.
     */
    private GenesisManager genesisManager;

    /**
     * The reference of the Communication component (runnable). Offers to the
     * Agent layer a way to communicate.
     */
    private CommunicationManager communicationManager;

    /** The thread associated with the Communication component. */
    private Thread communicationThread;
    
    /** The thread associated with the Discovery component. */
    private Thread discoveryThread;

    /**
     * Creates a new instance of Place
     * 
     * @param args
     *            The command line args
     */
    public Place(String[] args) {
    	System.setProperty("java.net.preferIPv4Stack", "true");
    	System.setProperty("java.net.preferIPv6Addresses", "false");
        parseCmdLine(args);
        properties = loadProperties();
        if (properties == null) {
            System.exit(1);
        }
        loadLogger(properties);
        HermesLogger.log(HermesLogger.INFO, "Hermes v. 2.18 alpha, Copyright (C)2006 Flavio Corradini and Emanuela Merelli;");
        HermesLogger.log(HermesLogger.INFO, "Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone, Davide Lorenzoli, \n Leonardo Mariani, Francesca Piersigilli, Lorenzo Scortichini, Leonardo Vito.");
        HermesLogger.log(HermesLogger.INFO, "GNU Lesser Public License");
        HermesLogger.log(HermesLogger.INFO, "Hermes comes with ABSOLUTELY NO WARRANTY;");
        HermesLogger.log(HermesLogger.INFO, "This library is free software, and you are welcome to redistribute it under it under \n the terms of the GNU Lesser General Public License as published by the Free Software Foundation,\n version 2.1 of the License");
        HermesLogger.log(HermesLogger.DEBUG, "Properties loaded: " + properties);
        /* Carico il sendReceive giusto! (Open or SSL) */
        //sendReceive = new SendReceive(currentPlaceAddress,properties);
        sendReceive = loadNet(properties);
        //Now i can have the currentPlaceAddress
        currentPlaceDescriptor = sendReceive.getCurrentPlaceDescriptor();
        /* Creazione dei link tra componenti */
        agentStarter = new AgentStarter(currentPlaceDescriptor.getPlaceAddress(), properties);
        idManager = new IdManager(currentPlaceDescriptor.getPlaceAddress());
        //Livello BasicServices
        mobilityManager = new MobilityManager(currentPlaceDescriptor.getPlaceAddress());
        communicationManager = new CommunicationManager(currentPlaceDescriptor.getPlaceAddress());
        discoveryManager = new DiscoveryManager();
        securityManager = new SecurityManager();
        genesisManager = new GenesisManager(currentPlaceDescriptor.getPlaceAddress());
        //Associazioni livello core
        sendReceive.setStarter(agentStarter);
        sendReceive.setTrace(idManager);
        agentStarter.setIdentify(idManager);
        agentStarter.setTrace(idManager);
        //Associazioni livello BasicServices
        mobilityManager.setEnvelopeIO(sendReceive);
        mobilityManager.setIdentify(idManager);
        mobilityManager.setTrace(idManager);
        discoveryManager.setIdentify(idManager);
        discoveryManager.setPlaceDescriptorIO(sendReceive);
        discoveryThread = new Thread(discoveryManager);
        discoveryThread.start();
        genesisManager.setStarter(agentStarter);
        genesisManager.setIdentify(idManager);
        genesisManager.setTrace(idManager);
        genesisManager.setEnvelopeIO(sendReceive);
        communicationManager.setEnvelopeIO(sendReceive);
        communicationManager.setTrace(idManager);
        communicationThread = new Thread(communicationManager);
        communicationThread.start();
        //Creazione della classe contenente i link ai servizi
        //offerti all'agent layer;
        BasicServices basicServices = new BasicServices(mobilityManager, communicationManager, communicationManager, discoveryManager, discoveryManager,discoveryManager, genesisManager, securityManager);
        agentStarter.setBasicServices(basicServices);

        if (properties.getProperty("HERMES.LOAD_SERVICEAGENTS").equals("true")) {

            HermesLogger.log(HermesLogger.DEBUG, "Starting to load Service Agents");
            loadServiceAgents();
            HermesLogger.log(HermesLogger.DEBUG, "Service Agents loaded");
        }
        if (properties.getProperty("HERMES.GUI").equals("true")) {
            HermesLogger.log(HermesLogger.DEBUG, "Starting GUI");
            Monitor monitor = new Monitor(currentPlaceDescriptor.getPlaceAddress(), properties.getProperty("HERMES.STORE_DIRECTORY"));
            monitor.setIdentify(idManager);
            monitor.setTrace(idManager);
            monitor.setBasicServices(basicServices);
            monitor.showGui();
        }
        HermesLogger.log(HermesLogger.INFO, "Place ready!");
    }

    /**
     * Launchs new agent directly from the Place instance using the Starter
     * component.
     * 
     * @param agent
     *            The agent to start.
     */
    public void launchUserAgent(Agent agent) {
        if (agentStarter == null) {
            HermesLogger.log(HermesLogger.WARNING, "No Starter component found!");
        } else {
            agentStarter.startAgent(agent, Agent.INIT, false);
        }
    }

    /**
     * This methods is use to launch into the place a service agent. It is used
     * whe the place starts.
     * 
     * @param agent
     *            The service agent to start.
     */
    private void launchServiceAgent(Agent agent) {
        if (agentStarter == null) {
            HermesLogger.log(HermesLogger.WARNING, "No Starter component found!");
        } else {
            agentStarter.startAgent(agent, Agent.INIT, true);
        }
    }

    /**
     * It loads every service agents stored into the HERMES.SERVICES directory.
     * It uses the launchServiceAgent method.
     */
    private void loadServiceAgents() {
        Class<?> clsAgent = null;
        String className = null;
        String fileName = null;
        ServiceAgent serviceAgent = null;
        try {
            String saPath = properties.getProperty("HERMES.SERVICES_DIRECTORY");
            File dir = new File(saPath);
            File[] files = dir.listFiles();
            int numFiles = files.length;

            //vedere di caricare solo agenti e non classi
            for (int i = 0; i < numFiles; i++) {
                fileName = files[i].getName();
                if (fileName.endsWith(".class")) {
                    //Get the service agent's name (file name without .class)
                    className = fileName.substring(0, fileName.indexOf(".class"));
                    //Load the service agent class from the service directory
                    clsAgent = Class.forName("hermesV2.services." + className);
                    if (clsAgent.getSuperclass().getName().endsWith("hermesV2.agent.ServiceAgent")) {
                        serviceAgent = (ServiceAgent) (clsAgent.getConstructor(new Class[] { String.class }).newInstance(new Object[] { className }));
                        launchServiceAgent(serviceAgent);
                    }
                }
            }
        } catch (ClassNotFoundException cnfe) {
            HermesLogger.log(HermesLogger.WARNING, "ClassNotFoundException", cnfe);
        } catch (NoSuchMethodException nsme) {
            HermesLogger.log(HermesLogger.WARNING, "NoSuchMethodException", nsme);
        } catch (IllegalAccessException iae) {
            HermesLogger.log(HermesLogger.WARNING, "IllegalAccessException", iae);
        } catch (InvocationTargetException ite) {
            HermesLogger.log(HermesLogger.WARNING, "InvocationTargetException", ite);
        } catch (Exception e) {
            HermesLogger.log(HermesLogger.WARNING, "Exception", e);
        }
    }

    /**
     * Main entry point when <code>Place</code> is used as a standalone
     * application.
     * 
     * @param args
     *            command line arguments
     */
    public static void main(String[] args) {
        // build a new place
        new Place(args);
    }

    /**
     * Returns the home directory of the current place.
     * 
     * @return the home directory of the current place.
     */
    public static String getHermesHome() {
        return hermesHome;
    }

    /**
     * Returns the current place descriptor.
     * 
     * @return the current placeDescriptor.
     */
    public static PlaceDescriptor getCurrentPlaceDescriptor() {
        return currentPlaceDescriptor;
    }

    /**
     * Parse command line switches.
     * 
     * @param args
     *            command line arguments
     */
    private void parseCmdLine(String[] args) {
        if (args == null) {
            hermesHome = System.getProperty("user.dir");
            hermesHome = hermesHome + System.getProperty("file.separator") + "hermesV2";
        } else {
            if (args.length == 0) {
                //Guess the right directory
                hermesHome = System.getProperty("user.dir");
                hermesHome = hermesHome + System.getProperty("file.separator") + "hermesV2";
            } else {
                for (int i = 0; i < args.length; i++) {
                    if (args[i].equals("-?")) {
                        System.out.println("Syntax: java yourPlaceNameClass -h hermesHome [-?]");
                        System.out.println("        -h hermesHome");
                        System.out.println("        -? this message");
                        System.exit(0);
                    } else if (args[i].equals("-h")) {
                        if (++i >= args.length) {
                            System.out.println("hermesHome null");
                            System.exit(0);
                        } else {
                            hermesHome = args[i];
                            File file = new File(hermesHome);
                            if (!file.isDirectory()) {
                                System.out.println("Exception: " + hermesHome + " is not the correct platform home");
                                System.exit(0);
                            }
                        }
                    } else {
                        System.out.println("Exception: option doesn't valid: " + args[i]);
                        System.out.println("Syntax: java yourPlaceName -h hermesHome [-?]");
                        System.out.println("        -h hermesHome");
                        System.out.println("        -? this message");
                        System.exit(0);
                    }
                }
            }
        }
    }

    /**
     * Load properties from a file
     * 
     * @return properties loaded.
     */
    private Properties loadProperties() {
        Properties properties = null;
        try {
            properties = LoadProperties.loadProperties(hermesHome, filePropertiesName);
        } catch (FileNotFoundException fnfe) {
            System.out.println("The properties file: " + hermesHome + "conf" + filePropertiesName + " not found\n" + fnfe);
            properties = null;
        } catch (Exception e) {
            System.out.println("Something wrong in the properties file:" + hermesHome + "\\conf\\" + filePropertiesName + "\n" + e);
            properties = null;
        }
        return properties;
    }

    /**
     * Sets the right logger from the property.
     * 
     * @param properties
     *            The properties of the place.
     */
    private void loadLogger(Properties properties) {
        //	I'm going to set the logger
        if (properties.getProperty("HERMES.LOGS").equals("true")) {
            HermesLogger.reset();
            String loggerLevel = properties.getProperty("HERMES.LOGS_LEVEL");
            HermesLogger.setLevel(loggerLevel);
            try {
                Class<?> loggerClass = Class.forName(properties.getProperty("HERMES.LOGS_CLASS"));
                loggerClass.getConstructor(new Class[] { java.lang.String.class }).newInstance(new Object[] { properties.getProperty("HERMES.LOGS_PROPERTYFILE") });
            } catch (ClassNotFoundException cne) {
                cne.printStackTrace();
                System.exit(1);
            } catch (IllegalAccessException iae) {
                iae.printStackTrace();
                System.exit(1);
            } catch (InstantiationException ie) {
                ie.printStackTrace();
                System.exit(1);
            } catch (InvocationTargetException ite) {
                ite.printStackTrace();
                System.exit(1);
            } catch (NoSuchMethodException nsme) {
                nsme.printStackTrace();
                System.exit(1);
            }
        } else {
            HermesLogger.setLevel(0);
        }
    }

    /**
     * Loads the right net component from the properties of the current place.
     * 
     * @param properties
     *            The properties object of the current place.
     * @return The net component.
     */
    private SendReceive loadNet(Properties properties) {
        SendReceive sendReceiverObj = null;
        try {
            Class<?> sendReceiveClass = Class.forName(properties.getProperty("HERMES.NET_CLASS"));
            Object obj = sendReceiveClass.getConstructor(new Class[] { java.lang.String.class, java.util.Properties.class }).newInstance(new Object[] { properties.getProperty("HERMES.NET_PROPERTYFILE"), properties });
            sendReceiverObj = (SendReceive) obj;
        } catch (ClassNotFoundException cne) {
            HermesLogger.log(HermesLogger.WARNING, "ClassNotFoundException", cne);
            System.exit(1);
        } catch (IllegalAccessException iae) {
            HermesLogger.log(HermesLogger.WARNING, "IllegalAccessException", iae);
            System.exit(1);
        } catch (InstantiationException ie) {
            HermesLogger.log(HermesLogger.WARNING, "InstantiationException", ie);
            System.exit(1);
        } catch (InvocationTargetException ite) {
            HermesLogger.log(HermesLogger.WARNING, "InvocationTargetException", ite);
            System.exit(1);
        } catch (NoSuchMethodException nsme) {
            HermesLogger.log(HermesLogger.WARNING, "NoSuchMethodException", nsme);
            System.exit(1);
        }
        return sendReceiverObj;
    }
}